
#pragma once

#include "Common.h"

#include <ttg/services.h>
#include <ttg/pipeline.h>

using namespace ttg;
using namespace ttg::pipeline;

//Slpits required power into ranges for each core.
//In future, will be replaced by data-repacking.
class ttgSplitter :public Node<size_t, std::pair<size_t, size_t>>
{
	void process(size_t data)
	{
		data -= 2;
		size_t core_count = services::CPUAnalyzer::getRef()->getCoreCount() * 4;
		for (size_t i = 0; i < core_count; i++)
		{
			std::pair<size_t, size_t> range;
			range.first = (i * data) / core_count;
			range.second = ((i + 1) * data) / core_count;
			if (range.first != range.second)
				sendNext(range);
		}
	}
};

//Multiplies special matrices.
template <class T>
class ttgMultiplier :public Node<std::pair<size_t, size_t>, Matrix2x2<T>>
{
	void process(std::pair<size_t, size_t> data)
	{
		Matrix2x2<T> special = Matrix2x2<T>(1, 1, 1, 0);
		Matrix2x2<T> res = special;
		for (size_t i = data.first; i < data.second; i++)
			res = res * special;
		sendNext(res);
	}
};

//Gatheres received matrices and multiplies then.
//Result will be sent by onEnd event.
template <class T>
class ttgGatherer :public Node<Matrix2x2<T>, Matrix2x2<T>>
{
	private:
		Matrix2x2<T> res;
	public:
		void onBegin()
		{ res = Matrix2x2<T>(1, 0, 0, 1); }
		void onEnd()
		{ sendNext(res); }
		void process(Matrix2x2<T> data)
		{ res = res * data; }
		inline ttgGatherer()
			:Node("ttgGatherer", true)
		{ /*nothing*/ }
};

//Calculates n-th Fibonacci number using ttg::pipeline.
//Returns number + elapsed time (in seconds).
template <class WorkType>
std::pair<WorkType, double> ttgFibonacci(size_t n)
{
	//Creating pipeline.
	Pipeline<size_t, Matrix2x2<WorkType>> pl(wrap(new ttgSplitter()) +
											 wrap(new ttgMultiplier<WorkType>()) +
											 wrap(new ttgGatherer<WorkType>()));
	//Launching.
	double t_start = services::Timer::getRef()->getSeconds();
	Matrix2x2<WorkType> res = pl.run(n)[0];
	double t_end = services::Timer::getRef()->getSeconds();
	//Returning result.
	return std::make_pair(res(0, 0), t_end - t_start);
}
